/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"
#include <stack>
using std::stack;

struct FallbackChainItem {
	string keyword;
	string arg;
};
static void parseFallbackChain(vector<FallbackChainItem> &chain, string description);

template<class T>
set<T*> fallbackSet(const vector<FallbackChainItem> &fallbacks, set<T*>(*func)(string desc, string arg))
{
	for(unsigned ii=0; ii<fallbacks.size(); ii++)
	{
		// Normally, a selector which yields the empty set is ignored, and the
		// next selector in the chain is chosen. As a special case exception,
		// "none" yields the empty set and doesn't continue.
		if(fallbacks[ii].keyword == "none")
			break;
		
		set<T*> results = func(fallbacks[ii].keyword, fallbacks[ii].arg);
		if(results.size() > 0)
			return results;
	}
	
	return set<T*>();
}

set<VirtualDesktop*> findDesks(string description)
{
	vector<FallbackChainItem> fallbackChain;
	parseFallbackChain(fallbackChain, description);
	parseFallbackChain(fallbackChain, "clicked/next/prev/current");
	return fallbackSet(fallbackChain, deskByDescription);
}

set<Monitor*> findMonitors(string description)
{
	vector<FallbackChainItem> fallbackChain;
	parseFallbackChain(fallbackChain, description);
	parseFallbackChain(fallbackChain, "cursor");
	return fallbackSet(fallbackChain, monitorByDescription);
}

set<WindowData*> findWindows(string description)
{
	vector<FallbackChainItem> fallbackChain;
	parseFallbackChain(fallbackChain, description);
	parseFallbackChain(fallbackChain, "clicked/focused");
	return fallbackSet(fallbackChain, windowByDescription);
}

set<ScreenbarPanel*> findPanels(string description)
{
	vector<FallbackChainItem> fallbackChain;
	parseFallbackChain(fallbackChain, description);
	return fallbackSet(fallbackChain, panelByDescription);
}

VirtualDesktop* findSingleDesk(string description)
{
	set<VirtualDesktop*> desks = findDesks(description);
	return (desks.size() == 1) ? *desks.begin() : NULL;
}

Monitor* findSingleMonitor(string description)
{
	set<Monitor*> monitors = findMonitors(description);
	return (monitors.size() == 1) ? *monitors.begin() : NULL;
}

WindowData* findSingleWindow(string description)
{
	set<WindowData*> windows = findWindows(description);
	return (windows.size() == 1) ? *windows.begin() : NULL;
}

ScreenbarPanel *findSinglePanel(string description)
{
	set<ScreenbarPanel*> panels = findPanels(description);
	return (panels.size() == 1) ? *panels.begin() : NULL;
}

/// This is a terribly dreadful hack for something that ought to be done with
/// lex and yacc, but isn't, because I don't want to add those tools as a
/// dependency. It takes a string like
///     abc/def(asdf)/ghi(j(k))
/// and splits it into a list (corresponding to unparenthesized slashes) of
/// pairs, where the first item in the pair is the part outside the parentheses
/// and the second item is the part inside, or if there are no parentheses, the
/// empty string.
void parseFallbackChain(vector<FallbackChainItem> &chain, string description)
{
	// Find the partner to every open parenthese, and make sure all
	// parentheses are matched.
	std::stack<int> unclosedParens;
	map<int,int> parenPairs;
	
	for(unsigned ii=0; ii<description.size(); ii++)
	{
		if(description[ii]=='(') {
			unclosedParens.push(ii);
		} else if(description[ii]==')') {
			if(unclosedParens.size() > 0) {
				parenPairs[unclosedParens.top()] = ii;
				unclosedParens.pop();
			} else {
				string warning = retprintf("Mismatches parentheses in '%s'.", description.c_str());
				warn(warning.c_str());
				return;
			}
		}
	}
	if(unclosedParens.size()) {
		string warning = retprintf("Mismatches parentheses in '%s'.", description.c_str());
		warn(warning.c_str());
		return;
	}
	
	// Split the string into keywords and arguments
	int keywordStart = 0;
	string keyword="";
	string arg="";
	for(unsigned ii=0; ii<=description.size(); ii++)
	{
		if(ii==description.size() || description[ii]=='/')
		{
			if(keyword=="")
				keyword = description.substr(keywordStart, ii-keywordStart);
			
			keywordStart = ii+1;
			
			FallbackChainItem selector = {keyword, arg};
			if(keyword.size() > 0) {
				chain.push_back(selector);
			}
			keyword = arg = "";
		}
		else if(description[ii]=='(')
		{
			keyword = description.substr(keywordStart, ii-keywordStart);
			arg = description.substr(ii+1, parenPairs[ii]-ii-1);
			ii = parenPairs[ii];
		}
	}
}
